﻿using System.Threading;
using FinMkts.Messaging.Pgm;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace FinMkts.Messaging.UnitTests
{
    [TestClass]
    public class BlockBufferedBlockingQueueTest
    {
        public TestContext TestContext { get; set; }

        #region Additional test attributes
        // 
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //Use ClassCleanup to run code after all tests in a class have run
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //Use TestInitialize to run code before running each test
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //Use TestCleanup to run code after each test has run
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion

        [TestMethod, ExpectedException(typeof(ArgumentNullException))]
        public void Constructor_Throws_ForNullCallback()
        {
            var blockSize = new BinarySize(1); // TODO: Initialize to an appropriate value
            Action<ByteBufferCollection> callback = null; // TODO: Initialize to an appropriate value
            new BlockBufferedBlockingQueue(blockSize, callback);
        }

        [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void Constructor_Throws_ForZeroBlockSize()
        {
            new BlockBufferedBlockingQueue(BinarySize.Zero, block => { });
        }

        [TestMethod, ExpectedException(typeof(ArgumentNullException))]
        public void Add_Throws_ForNullData()
        {
            var blockSize = new BinarySize(1024);
            var queue = new BlockBufferedBlockingQueue(blockSize, block => { });
            queue.Add(null);
        }

        [TestMethod]
        public void Add_DoesNothing_ForZeroSizeData()
        {
            var blockSize = new BinarySize(1024);
            var queue = new BlockBufferedBlockingQueue(blockSize, block => { });
            queue.Add(new byte[0]);
            Assert.AreEqual(0, queue.Count);
        }

        [TestMethod]
        public void Add_CallsCallback_WhenNotBlocked()
        {
            var blockSize = new BinarySize(1024);
            ByteBufferCollection bytes = null;
            var queue = new BlockBufferedBlockingQueue(blockSize, block => bytes = block);
            Assert.IsFalse(queue.IsBlocked);
            var expected = new byte[1];
            
            queue.Add(expected);

            Assert.IsFalse(queue.IsBlocked);
            Assert.IsNotNull(bytes);
            Assert.AreEqual(1, bytes.Count);
            Assert.AreEqual(expected, bytes[0].Array);
            Assert.AreEqual(0, bytes[0].Offset);
            Assert.AreEqual(expected.Length, bytes[0].Count);
        }

        [TestMethod]
        public void Add_DoesNotCallCallback_WhenBlockedAndEmpty()
        {
            var blockSize = new BinarySize(1024);
            ByteBufferCollection bytes = null;
            var queue = new BlockBufferedBlockingQueue(blockSize, block => bytes = block);
            queue.IsBlocked = true;
            var expected = new byte[1];
            queue.Add(expected);
            Assert.IsNull(bytes);
            Assert.AreEqual(1, queue.Count);
        }

        [TestMethod]
        public void Add_Blocks_WhenBlockedAndFull()
        {
            ByteBufferCollection bytes = null;
            var queue = new BlockBufferedBlockingQueue(BinarySize.FromBytes(32), block => bytes = block);
            queue.IsBlocked = true;
            // first add will call backback
            var expected = new byte[30];
            queue.Add(expected);

            var t = new Thread(() => queue.Add(new byte[30]));
            t.Start();
            Assert.IsFalse(t.Join(TimeSpan.FromSeconds(0.2)));
            Assert.AreEqual(1, queue.Count);
            queue.TakeNextBlock();
            Assert.IsTrue(t.Join(TimeSpan.FromSeconds(0.2)));
            Assert.AreEqual(1, queue.Count);
        }

        [TestMethod()]
        public void TakeNextBlock_ReturnsNull_WhenQueueIsEmpty()
        {
            var queue = new BlockBufferedBlockingQueue(BinarySize.FromBytes(32), block => { });
            queue.IsBlocked = true;
            var actual = queue.TakeNextBlock();
            Assert.IsNull(actual);
        }

        [TestMethod()]
        public void TakeNextBlock_ReturnsAllQueued_WhenQueueIsNotEmpty()
        {
            ByteBufferCollection bytes = null;
            var queue = new BlockBufferedBlockingQueue(BinarySize.FromBytes(32), block => bytes = block);
            queue.IsBlocked = true;
            queue.Add(new byte[2]);
            queue.Add(new byte[4]);
            var actual = queue.TakeNextBlock();
            Assert.IsNotNull(actual);
            Assert.AreEqual(2, actual.Count);
            Assert.AreEqual(2, actual[0].Count);
            Assert.AreEqual(4, actual[1].Count);
            Assert.IsNull(bytes);
        }

    }
}
